
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


typedef struct
{
    ngx_array_t *codes; /* uintptr_t */

    ngx_uint_t stack_size;

    ngx_flag_t log;
    ngx_flag_t uninitialized_variable_warn;
} ngx_http_rewrite_loc_conf_t;


static void     *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf);
static char     *ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf, void *parent,
                                                 void *child);
static ngx_int_t ngx_http_rewrite_init(ngx_conf_t *cf);
static char *ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd,
                                     void *conf);
static char *ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd,
                                    void *conf);
static char *ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd,
                                 void *conf);
static char *ngx_http_rewrite_if_condition(ngx_conf_t                  *cf,
                                           ngx_http_rewrite_loc_conf_t *lcf);
static char *ngx_http_rewrite_variable(ngx_conf_t                  *cf,
                                       ngx_http_rewrite_loc_conf_t *lcf,
                                       ngx_str_t                   *value);
static char *ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd,
                                  void *conf);
static char *ngx_http_rewrite_value(ngx_conf_t                  *cf,
                                    ngx_http_rewrite_loc_conf_t *lcf,
                                    ngx_str_t                   *value);


static ngx_command_t ngx_http_rewrite_commands[] = {

    {ngx_string("rewrite"),
     NGX_HTTP_SRV_CONF | NGX_HTTP_SIF_CONF | NGX_HTTP_LOC_CONF
         | NGX_HTTP_LIF_CONF | NGX_CONF_TAKE23,
     ngx_http_rewrite, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL},

    {ngx_string("return"),
     NGX_HTTP_SRV_CONF | NGX_HTTP_SIF_CONF | NGX_HTTP_LOC_CONF
         | NGX_HTTP_LIF_CONF | NGX_CONF_TAKE12,
     ngx_http_rewrite_return, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL},

    {ngx_string("break"),
     NGX_HTTP_SRV_CONF | NGX_HTTP_SIF_CONF | NGX_HTTP_LOC_CONF
         | NGX_HTTP_LIF_CONF | NGX_CONF_NOARGS,
     ngx_http_rewrite_break, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL},

    {ngx_string("if"),
     NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_BLOCK | NGX_CONF_1MORE,
     ngx_http_rewrite_if, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL},

    {ngx_string("set"),
     NGX_HTTP_SRV_CONF | NGX_HTTP_SIF_CONF | NGX_HTTP_LOC_CONF
         | NGX_HTTP_LIF_CONF | NGX_CONF_TAKE2,
     ngx_http_rewrite_set, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL},

    {ngx_string("rewrite_log"),
     NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_SIF_CONF
         | NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_CONF_FLAG,
     ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET,
     offsetof(ngx_http_rewrite_loc_conf_t, log), NULL},

    {ngx_string("uninitialized_variable_warn"),
     NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_SIF_CONF
         | NGX_HTTP_LOC_CONF | NGX_HTTP_LIF_CONF | NGX_CONF_FLAG,
     ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET,
     offsetof(ngx_http_rewrite_loc_conf_t, uninitialized_variable_warn), NULL},

    ngx_null_command};


static ngx_http_module_t ngx_http_rewrite_module_ctx = {
    NULL,                  /* preconfiguration */
    ngx_http_rewrite_init, /* postconfiguration */

    NULL, /* create main configuration */
    NULL, /* init main configuration */

    NULL, /* create server configuration */
    NULL, /* merge server configuration */

    ngx_http_rewrite_create_loc_conf, /* create location configuration */
    ngx_http_rewrite_merge_loc_conf   /* merge location configuration */
};


ngx_module_t ngx_http_rewrite_module = {
    NGX_MODULE_V1,
    &ngx_http_rewrite_module_ctx, /* module context */
    ngx_http_rewrite_commands,    /* module directives */
    NGX_HTTP_MODULE,              /* module type */
    NULL,                         /* init master */
    NULL,                         /* init module */
    NULL,                         /* init process */
    NULL,                         /* init thread */
    NULL,                         /* exit thread */
    NULL,                         /* exit process */
    NULL,                         /* exit master */
    NGX_MODULE_V1_PADDING};


static ngx_int_t
ngx_http_rewrite_handler(ngx_http_request_t *r)
{
    ngx_int_t                    index;
    ngx_http_script_code_pt      code;
    ngx_http_script_engine_t    *e;
    ngx_http_core_srv_conf_t    *cscf;
    ngx_http_core_main_conf_t   *cmcf;
    ngx_http_rewrite_loc_conf_t *rlcf;

    cmcf  = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    cscf  = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
    index = cmcf->phase_engine.location_rewrite_index;

    if (r->phase_handler == index && r->loc_conf == cscf->ctx->loc_conf)
    {
        /* skipping location rewrite phase for server null location */
        return NGX_DECLINED;
    }

    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);

    if (rlcf->codes == NULL)
    {
        return NGX_DECLINED;
    }

    e = ngx_pcalloc(r->pool, sizeof(ngx_http_script_engine_t));
    if (e == NULL)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    e->sp = ngx_pcalloc(r->pool,
                        rlcf->stack_size * sizeof(ngx_http_variable_value_t));
    if (e->sp == NULL)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    e->ip      = rlcf->codes->elts;
    e->request = r;
    e->quote   = 1;
    e->log     = rlcf->log;
    e->status  = NGX_DECLINED;

    while (*(uintptr_t *)e->ip)
    {
        code = *(ngx_http_script_code_pt *)e->ip;
        code(e);
    }

    return e->status;
}


static ngx_int_t
ngx_http_rewrite_var(ngx_http_request_t *r, ngx_http_variable_value_t *v,
                     uintptr_t data)
{
    ngx_http_variable_t         *var;
    ngx_http_core_main_conf_t   *cmcf;
    ngx_http_rewrite_loc_conf_t *rlcf;

    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);

    if (rlcf->uninitialized_variable_warn == 0)
    {
        *v = ngx_http_variable_null_value;
        return NGX_OK;
    }

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    var = cmcf->variables.elts;

    /*
     * the ngx_http_rewrite_module sets variables directly in r->variables,
     * and they should be handled by ngx_http_get_indexed_variable(),
     * so the handler is called only if the variable is not initialized
     */

    ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
                  "using uninitialized \"%V\" variable", &var[data].name);

    *v = ngx_http_variable_null_value;

    return NGX_OK;
}


static void *
ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_rewrite_loc_conf_t *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rewrite_loc_conf_t));
    if (conf == NULL)
    {
        return NULL;
    }

    conf->stack_size                  = NGX_CONF_UNSET_UINT;
    conf->log                         = NGX_CONF_UNSET;
    conf->uninitialized_variable_warn = NGX_CONF_UNSET;

    return conf;
}


static char *
ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_rewrite_loc_conf_t *prev = parent;
    ngx_http_rewrite_loc_conf_t *conf = child;

    uintptr_t *code;

    ngx_conf_merge_value(conf->log, prev->log, 0);
    ngx_conf_merge_value(conf->uninitialized_variable_warn,
                         prev->uninitialized_variable_warn, 1);
    ngx_conf_merge_uint_value(conf->stack_size, prev->stack_size, 10);

    if (conf->codes == NULL)
    {
        return NGX_CONF_OK;
    }

    if (conf->codes == prev->codes)
    {
        return NGX_CONF_OK;
    }

    code = ngx_array_push_n(conf->codes, sizeof(uintptr_t));
    if (code == NULL)
    {
        return NGX_CONF_ERROR;
    }

    *code = (uintptr_t)NULL;

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_http_rewrite_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt       *h;
    ngx_http_core_main_conf_t *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers);
    if (h == NULL)
    {
        return NGX_ERROR;
    }

    *h = ngx_http_rewrite_handler;

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
    if (h == NULL)
    {
        return NGX_ERROR;
    }

    *h = ngx_http_rewrite_handler;

    return NGX_OK;
}


static char *
ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;

    ngx_str_t                        *value;
    ngx_uint_t                        last;
    ngx_regex_compile_t               rc;
    ngx_http_script_code_pt          *code;
    ngx_http_script_compile_t         sc;
    ngx_http_script_regex_code_t     *regex;
    ngx_http_script_regex_end_code_t *regex_end;
    u_char                            errstr[NGX_MAX_CONF_ERRSTR];

    regex = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                       sizeof(ngx_http_script_regex_code_t));
    if (regex == NULL)
    {
        return NGX_CONF_ERROR;
    }

    ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t));

    value = cf->args->elts;

    if (value[2].len == 0)
    {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty replacement");
        return NGX_CONF_ERROR;
    }

    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

    rc.pattern  = value[1];
    rc.err.len  = NGX_MAX_CONF_ERRSTR;
    rc.err.data = errstr;

    /* TODO: NGX_REGEX_CASELESS */

    regex->regex = ngx_http_regex_compile(cf, &rc);
    if (regex->regex == NULL)
    {
        return NGX_CONF_ERROR;
    }

    regex->code = ngx_http_script_regex_start_code;
    regex->uri  = 1;
    regex->name = value[1];

    if (value[2].data[value[2].len - 1] == '?')
    {
        /* the last "?" drops the original arguments */
        value[2].len--;
    }
    else
    {
        regex->add_args = 1;
    }

    last = 0;

    if (ngx_strncmp(value[2].data, "http://", sizeof("http://") - 1) == 0
        || ngx_strncmp(value[2].data, "https://", sizeof("https://") - 1) == 0
        || ngx_strncmp(value[2].data, "$scheme", sizeof("$scheme") - 1) == 0)
    {
        regex->status   = NGX_HTTP_MOVED_TEMPORARILY;
        regex->redirect = 1;
        last            = 1;
    }

    if (cf->args->nelts == 4)
    {
        if (ngx_strcmp(value[3].data, "last") == 0)
        {
            last = 1;
        }
        else if (ngx_strcmp(value[3].data, "break") == 0)
        {
            regex->break_cycle = 1;
            last               = 1;
        }
        else if (ngx_strcmp(value[3].data, "redirect") == 0)
        {
            regex->status   = NGX_HTTP_MOVED_TEMPORARILY;
            regex->redirect = 1;
            last            = 1;
        }
        else if (ngx_strcmp(value[3].data, "permanent") == 0)
        {
            regex->status   = NGX_HTTP_MOVED_PERMANENTLY;
            regex->redirect = 1;
            last            = 1;
        }
        else
        {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
                               &value[3]);
            return NGX_CONF_ERROR;
        }
    }

    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

    sc.cf               = cf;
    sc.source           = &value[2];
    sc.lengths          = &regex->lengths;
    sc.values           = &lcf->codes;
    sc.variables        = ngx_http_script_variables_count(&value[2]);
    sc.main             = regex;
    sc.complete_lengths = 1;
    sc.compile_args     = !regex->redirect;

    if (ngx_http_script_compile(&sc) != NGX_OK)
    {
        return NGX_CONF_ERROR;
    }

    regex = sc.main;

    regex->size = sc.size;
    regex->args = sc.args;

    if (sc.variables == 0 && !sc.dup_capture)
    {
        regex->lengths = NULL;
    }

    regex_end = ngx_http_script_add_code(
        lcf->codes, sizeof(ngx_http_script_regex_end_code_t), &regex);
    if (regex_end == NULL)
    {
        return NGX_CONF_ERROR;
    }

    regex_end->code     = ngx_http_script_regex_end_code;
    regex_end->uri      = regex->uri;
    regex_end->args     = regex->args;
    regex_end->add_args = regex->add_args;
    regex_end->redirect = regex->redirect;

    if (last)
    {
        code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), &regex);
        if (code == NULL)
        {
            return NGX_CONF_ERROR;
        }

        *code = NULL;
    }

    regex->next =
        (u_char *)lcf->codes->elts + lcf->codes->nelts - (u_char *)regex;

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;

    u_char                          *p;
    ngx_str_t                       *value, *v;
    ngx_http_script_return_code_t   *ret;
    ngx_http_compile_complex_value_t ccv;

    ret = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                     sizeof(ngx_http_script_return_code_t));
    if (ret == NULL)
    {
        return NGX_CONF_ERROR;
    }

    value = cf->args->elts;

    ngx_memzero(ret, sizeof(ngx_http_script_return_code_t));

    ret->code = ngx_http_script_return_code;

    p = value[1].data;

    ret->status = ngx_atoi(p, value[1].len);

    if (ret->status == (uintptr_t)NGX_ERROR)
    {
        if (cf->args->nelts == 2
            && (ngx_strncmp(p, "http://", sizeof("http://") - 1) == 0
                || ngx_strncmp(p, "https://", sizeof("https://") - 1) == 0
                || ngx_strncmp(p, "$scheme", sizeof("$scheme") - 1) == 0))
        {
            ret->status = NGX_HTTP_MOVED_TEMPORARILY;
            v           = &value[1];
        }
        else
        {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid return code \"%V\"", &value[1]);
            return NGX_CONF_ERROR;
        }
    }
    else
    {
        if (ret->status > 999)
        {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid return code \"%V\"", &value[1]);
            return NGX_CONF_ERROR;
        }

        if (cf->args->nelts == 2)
        {
            return NGX_CONF_OK;
        }

        v = &value[2];
    }

    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

    ccv.cf            = cf;
    ccv.value         = v;
    ccv.complex_value = &ret->text;

    if (ngx_http_compile_complex_value(&ccv) != NGX_OK)
    {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;

    ngx_http_script_code_pt *code;

    code = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(uintptr_t));
    if (code == NULL)
    {
        return NGX_CONF_ERROR;
    }

    *code = ngx_http_script_break_code;

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;

    void                        *mconf;
    char                        *rv;
    u_char                      *elts;
    ngx_uint_t                   i;
    ngx_conf_t                   save;
    ngx_http_module_t           *module;
    ngx_http_conf_ctx_t         *ctx, *pctx;
    ngx_http_core_loc_conf_t    *clcf, *pclcf;
    ngx_http_script_if_code_t   *if_code;
    ngx_http_rewrite_loc_conf_t *nlcf;

    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
    if (ctx == NULL)
    {
        return NGX_CONF_ERROR;
    }

    pctx           = cf->ctx;
    ctx->main_conf = pctx->main_conf;
    ctx->srv_conf  = pctx->srv_conf;

    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    if (ctx->loc_conf == NULL)
    {
        return NGX_CONF_ERROR;
    }

    for (i = 0; cf->cycle->modules[i]; i++)
    {
        if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE)
        {
            continue;
        }

        module = cf->cycle->modules[i]->ctx;

        if (module->create_loc_conf)
        {
            mconf = module->create_loc_conf(cf);
            if (mconf == NULL)
            {
                return NGX_CONF_ERROR;
            }

            ctx->loc_conf[cf->cycle->modules[i]->ctx_index] = mconf;
        }
    }

    pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];

    clcf           = ctx->loc_conf[ngx_http_core_module.ctx_index];
    clcf->loc_conf = ctx->loc_conf;
    clcf->name     = pclcf->name;
    clcf->noname   = 1;

    if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK)
    {
        return NGX_CONF_ERROR;
    }

    if (ngx_http_rewrite_if_condition(cf, lcf) != NGX_CONF_OK)
    {
        return NGX_CONF_ERROR;
    }

    if_code = ngx_array_push_n(lcf->codes, sizeof(ngx_http_script_if_code_t));
    if (if_code == NULL)
    {
        return NGX_CONF_ERROR;
    }

    if_code->code = ngx_http_script_if_code;

    elts = lcf->codes->elts;


    /* the inner directives must be compiled to the same code array */

    nlcf        = ctx->loc_conf[ngx_http_rewrite_module.ctx_index];
    nlcf->codes = lcf->codes;


    save    = *cf;
    cf->ctx = ctx;

    if (cf->cmd_type == NGX_HTTP_SRV_CONF)
    {
        if_code->loc_conf = NULL;
        cf->cmd_type      = NGX_HTTP_SIF_CONF;
    }
    else
    {
        if_code->loc_conf = ctx->loc_conf;
        cf->cmd_type      = NGX_HTTP_LIF_CONF;
    }

    rv = ngx_conf_parse(cf, NULL);

    *cf = save;

    if (rv != NGX_CONF_OK)
    {
        return rv;
    }


    if (elts != lcf->codes->elts)
    {
        if_code = (ngx_http_script_if_code_t *)((u_char *)if_code
                                                + ((u_char *)lcf->codes->elts
                                                   - elts));
    }

    if_code->next =
        (u_char *)lcf->codes->elts + lcf->codes->nelts - (u_char *)if_code;

    /* the code array belong to parent block */

    nlcf->codes = NULL;

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_if_condition(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf)
{
    u_char                       *p;
    size_t                        len;
    ngx_str_t                    *value;
    ngx_uint_t                    cur, last;
    ngx_regex_compile_t           rc;
    ngx_http_script_code_pt      *code;
    ngx_http_script_file_code_t  *fop;
    ngx_http_script_regex_code_t *regex;
    u_char                        errstr[NGX_MAX_CONF_ERRSTR];

    value = cf->args->elts;
    last  = cf->args->nelts - 1;

    if (value[1].len < 1 || value[1].data[0] != '(')
    {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"",
                           &value[1]);
        return NGX_CONF_ERROR;
    }

    if (value[1].len == 1)
    {
        cur = 2;
    }
    else
    {
        cur = 1;
        value[1].len--;
        value[1].data++;
    }

    if (value[last].len < 1 || value[last].data[value[last].len - 1] != ')')
    {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"",
                           &value[last]);
        return NGX_CONF_ERROR;
    }

    if (value[last].len == 1)
    {
        last--;
    }
    else
    {
        value[last].len--;
        value[last].data[value[last].len] = '\0';
    }

    len = value[cur].len;
    p   = value[cur].data;

    if (len > 1 && p[0] == '$')
    {
        if (cur != last && cur + 2 != last)
        {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"",
                               &value[cur]);
            return NGX_CONF_ERROR;
        }

        if (ngx_http_rewrite_variable(cf, lcf, &value[cur]) != NGX_CONF_OK)
        {
            return NGX_CONF_ERROR;
        }

        if (cur == last)
        {
            return NGX_CONF_OK;
        }

        cur++;

        len = value[cur].len;
        p   = value[cur].data;

        if (len == 1 && p[0] == '=')
        {
            if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK)
            {
                return NGX_CONF_ERROR;
            }

            code = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                              sizeof(uintptr_t));
            if (code == NULL)
            {
                return NGX_CONF_ERROR;
            }

            *code = ngx_http_script_equal_code;

            return NGX_CONF_OK;
        }

#if (T_NGX_HTTP_IMPROVED_IF)
        if (len == 1 && p[0] == '>')
        {
            if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK)
            {
                return NGX_CONF_ERROR;
            }

            code = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                              sizeof(uintptr_t));
            if (code == NULL)
            {
                return NGX_CONF_ERROR;
            }

            *code = ngx_http_script_greater_code;

            return NGX_CONF_OK;
        }

        if (len == 1 && p[0] == '<')
        {
            if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK)
            {
                return NGX_CONF_ERROR;
            }

            code = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                              sizeof(uintptr_t));
            if (code == NULL)
            {
                return NGX_CONF_ERROR;
            }

            *code = ngx_http_script_less_code;

            return NGX_CONF_OK;
        }

        if (len == 2 && p[0] == '>' && p[1] == '=')
        {
            if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK)
            {
                return NGX_CONF_ERROR;
            }

            code = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                              sizeof(uintptr_t));
            if (code == NULL)
            {
                return NGX_CONF_ERROR;
            }

            *code = ngx_http_script_greater_or_equal_code;
            return NGX_CONF_OK;
        }

        if (len == 2 && p[0] == '<' && p[1] == '=')
        {
            if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK)
            {
                return NGX_CONF_ERROR;
            }

            code = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                              sizeof(uintptr_t));
            if (code == NULL)
            {
                return NGX_CONF_ERROR;
            }

            *code = ngx_http_script_less_or_equal_code;
            return NGX_CONF_OK;
        }
#endif

        if (len == 2 && p[0] == '!' && p[1] == '=')
        {
            if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK)
            {
                return NGX_CONF_ERROR;
            }

            code = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                              sizeof(uintptr_t));
            if (code == NULL)
            {
                return NGX_CONF_ERROR;
            }

            *code = ngx_http_script_not_equal_code;
            return NGX_CONF_OK;
        }

        if ((len == 1 && p[0] == '~')
            || (len == 2 && p[0] == '~' && p[1] == '*')
            || (len == 2 && p[0] == '!' && p[1] == '~')
            || (len == 3 && p[0] == '!' && p[1] == '~' && p[2] == '*'))
        {
            regex = ngx_http_script_start_code(
                cf->pool, &lcf->codes, sizeof(ngx_http_script_regex_code_t));
            if (regex == NULL)
            {
                return NGX_CONF_ERROR;
            }

            ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t));

            ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

            rc.pattern  = value[last];
            rc.options  = (p[len - 1] == '*') ? NGX_REGEX_CASELESS : 0;
            rc.err.len  = NGX_MAX_CONF_ERRSTR;
            rc.err.data = errstr;

            regex->regex = ngx_http_regex_compile(cf, &rc);
            if (regex->regex == NULL)
            {
                return NGX_CONF_ERROR;
            }

            regex->code = ngx_http_script_regex_start_code;
            regex->next = sizeof(ngx_http_script_regex_code_t);
            regex->test = 1;
            if (p[0] == '!')
            {
                regex->negative_test = 1;
            }
            regex->name = value[last];

            return NGX_CONF_OK;
        }

        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "unexpected \"%V\" in condition", &value[cur]);
        return NGX_CONF_ERROR;
    }
    else if ((len == 2 && p[0] == '-')
             || (len == 3 && p[0] == '!' && p[1] == '-'))
    {
        if (cur + 1 != last)
        {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"",
                               &value[cur]);
            return NGX_CONF_ERROR;
        }

        value[last].data[value[last].len] = '\0';
        value[last].len++;

        if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK)
        {
            return NGX_CONF_ERROR;
        }

        fop = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                         sizeof(ngx_http_script_file_code_t));
        if (fop == NULL)
        {
            return NGX_CONF_ERROR;
        }

        fop->code = ngx_http_script_file_code;

        if (p[1] == 'f')
        {
            fop->op = ngx_http_script_file_plain;
            return NGX_CONF_OK;
        }

        if (p[1] == 'd')
        {
            fop->op = ngx_http_script_file_dir;
            return NGX_CONF_OK;
        }

        if (p[1] == 'e')
        {
            fop->op = ngx_http_script_file_exists;
            return NGX_CONF_OK;
        }

        if (p[1] == 'x')
        {
            fop->op = ngx_http_script_file_exec;
            return NGX_CONF_OK;
        }

        if (p[0] == '!')
        {
            if (p[2] == 'f')
            {
                fop->op = ngx_http_script_file_not_plain;
                return NGX_CONF_OK;
            }

            if (p[2] == 'd')
            {
                fop->op = ngx_http_script_file_not_dir;
                return NGX_CONF_OK;
            }

            if (p[2] == 'e')
            {
                fop->op = ngx_http_script_file_not_exists;
                return NGX_CONF_OK;
            }

            if (p[2] == 'x')
            {
                fop->op = ngx_http_script_file_not_exec;
                return NGX_CONF_OK;
            }
        }

        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"",
                           &value[cur]);
        return NGX_CONF_ERROR;
    }

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid condition \"%V\"",
                       &value[cur]);

    return NGX_CONF_ERROR;
}


static char *
ngx_http_rewrite_variable(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,
                          ngx_str_t *value)
{
    ngx_int_t                   index;
    ngx_http_script_var_code_t *var_code;

    value->len--;
    value->data++;

    index = ngx_http_get_variable_index(cf, value);

    if (index == NGX_ERROR)
    {
        return NGX_CONF_ERROR;
    }

    var_code = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                          sizeof(ngx_http_script_var_code_t));
    if (var_code == NULL)
    {
        return NGX_CONF_ERROR;
    }

    var_code->code  = ngx_http_script_var_code;
    var_code->index = index;

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;

    ngx_int_t                           index;
    ngx_str_t                          *value;
    ngx_http_variable_t                *v;
    ngx_http_script_var_code_t         *vcode;
    ngx_http_script_var_handler_code_t *vhcode;

    value = cf->args->elts;

    if (value[1].data[0] != '$')
    {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"",
                           &value[1]);
        return NGX_CONF_ERROR;
    }

    value[1].len--;
    value[1].data++;

    v = ngx_http_add_variable(cf, &value[1],
                              NGX_HTTP_VAR_CHANGEABLE | NGX_HTTP_VAR_WEAK);
    if (v == NULL)
    {
        return NGX_CONF_ERROR;
    }

    index = ngx_http_get_variable_index(cf, &value[1]);
    if (index == NGX_ERROR)
    {
        return NGX_CONF_ERROR;
    }

    if (v->get_handler == NULL)
    {
        v->get_handler = ngx_http_rewrite_var;
        v->data        = index;
    }

    if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK)
    {
        return NGX_CONF_ERROR;
    }

    if (v->set_handler)
    {
        vhcode = ngx_http_script_start_code(
            cf->pool, &lcf->codes, sizeof(ngx_http_script_var_handler_code_t));
        if (vhcode == NULL)
        {
            return NGX_CONF_ERROR;
        }

        vhcode->code    = ngx_http_script_var_set_handler_code;
        vhcode->handler = v->set_handler;
        vhcode->data    = v->data;

        return NGX_CONF_OK;
    }

    vcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                       sizeof(ngx_http_script_var_code_t));
    if (vcode == NULL)
    {
        return NGX_CONF_ERROR;
    }

    vcode->code  = ngx_http_script_set_var_code;
    vcode->index = (uintptr_t)index;

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,
                       ngx_str_t *value)
{
    ngx_int_t                             n;
    ngx_http_script_compile_t             sc;
    ngx_http_script_value_code_t         *val;
    ngx_http_script_complex_value_code_t *complex;

    n = ngx_http_script_variables_count(value);

    if (n == 0)
    {
        val = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                         sizeof(ngx_http_script_value_code_t));
        if (val == NULL)
        {
            return NGX_CONF_ERROR;
        }

        n = ngx_atoi(value->data, value->len);

        if (n == NGX_ERROR)
        {
            n = 0;
        }

        val->code      = ngx_http_script_value_code;
        val->value     = (uintptr_t)n;
        val->text_len  = (uintptr_t)value->len;
        val->text_data = (uintptr_t)value->data;

        return NGX_CONF_OK;
    }

    complex = ngx_http_script_start_code(
        cf->pool, &lcf->codes, sizeof(ngx_http_script_complex_value_code_t));
    if (complex == NULL)
    {
        return NGX_CONF_ERROR;
    }

    complex->code    = ngx_http_script_complex_value_code;
    complex->lengths = NULL;

    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

    sc.cf               = cf;
    sc.source           = value;
    sc.lengths          = &complex->lengths;
    sc.values           = &lcf->codes;
    sc.variables        = n;
    sc.complete_lengths = 1;

    if (ngx_http_script_compile(&sc) != NGX_OK)
    {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}
